home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue50 / Except / hvyast32.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-09-06  |  9.5 KB  |  306 lines

  1. unit HVYAST32;
  2. // Yet-Another-Stack-Tracer, 32-bit version
  3. //
  4. // Loosely based on my 16-bit YAST code published in
  5. // The Delphi Magazine, issue 7.
  6. //
  7. // Description: A general call-back based stack-trace utility.
  8. // Both stack frames based and raw stack tracing is supported.
  9. //
  10. // Written by Hallvard Vassbotn, hallvard@balder.no, July 1999
  11. //
  12. interface
  13.  
  14. uses
  15.   Windows,
  16.   SysUtils;
  17.  
  18. // The generic stack tracing machinery
  19.  
  20. const
  21.   MaxBlock = MaxInt-$f;
  22. type
  23.   PBytes  = ^TBytes;
  24.   TBytes  = array[0..MaxBlock div SizeOf(byte)] of byte;
  25.   PDWORDS = ^TDWORDS;
  26.   TDWORDS = array[0..MaxBlock div SizeOf(DWORD)] of DWORD;
  27.   PStackFrame = ^TStackFrame;
  28.   TStackFrame = record
  29.     CallersEBP : DWORD;
  30.     CallerAdr  : DWORD;
  31.   end;
  32.   TStackInfo = record
  33.     CallerAdr  : DWORD;
  34.     Level      : DWORD;
  35.     CallersEBP : DWORD;
  36.     DumpSize   : DWORD;
  37.     ParamSize  : DWORD;
  38.     ParamPtr   : PDWORDS;
  39.     case integer of
  40.      0 : (StackFrame : PStackFrame);
  41.      1 : (DumpPtr    : PBytes);
  42.   end;
  43.   TReportStackFrame = function(var StackInfo: TStackInfo; PrivateData: Pointer): boolean;
  44.  
  45. procedure TraceStackFrames(ReportStackFrame: TReportStackFrame; PrivateData: Pointer);
  46. procedure TraceStackRaw(ReportStackFrame: TReportStackFrame; PrivateData: Pointer);
  47.  
  48. // Default stack tracer
  49.  
  50. const
  51.   MaxStackLevels = 50;
  52. type
  53.   TStackInfoArray = array[0..MaxStackLevels-1] of TStackInfo;
  54. var
  55.   StackDump: TStackInfoArray;
  56.   StackDumpCount: integer;
  57.  
  58. function PhysicalToLogical(Physical: DWORD): DWORD;
  59. function DefaultReportStackFrame(var StackInfo: TStackInfo; PrivateData: Pointer): boolean;
  60. procedure SaveStackTrace(Raw: boolean; IgnoreLevels: integer; FirstCaller: pointer);
  61.  
  62. implementation
  63.  
  64. uses
  65.   HVPEUtils;
  66.  
  67. {$W-} // This routine should not have a EBP stack frame
  68. function GetEBP: pointer;
  69. // Return the current contents of the EBP register
  70. asm
  71.   MOV EAX, EBP
  72. end;
  73.  
  74. function GetESP: pointer;
  75. // Return the current contents of the ESP register
  76. asm
  77.   MOV EAX, ESP
  78. end;
  79.  
  80. function GetStackTop: DWORD;
  81. asm
  82.   // Pick up the top of the stack from the Thread Information Block (TIB)
  83.   // pointed to by the FS segment register.
  84.   //
  85.   // Reference: Matt Pietrek, MSJ, Under the hood, on TIBs:
  86.   // PVOID pvStackUserTop  // 04h Top of user stack
  87.   // http:{msdn.microsoft.com/library/periodic/period96/periodic/msj/F1/D6/S2CE.htm }
  88.   //
  89.   MOV EAX, FS:[4]
  90. end;
  91.  
  92. var
  93.   TopOfStack : DWORD;
  94.   BaseOfStack: DWORD;
  95.   BaseOfCode : DWORD;
  96.   TopOfCode  : DWORD;
  97.  
  98. procedure InitGlobalVars;
  99. var
  100.   NTHeader: PImageNTHeaders;
  101. begin
  102.   { Get pointers into the EXE file image }
  103.   if BaseOfCode = 0 then
  104.   begin
  105.     NTHeader := GetImageNtHeader(Pointer(hInstance));
  106.     BaseOfCode := DWord(hInstance) + NTHeader.OptionalHeader.BaseOfCode;
  107.     TopOfCode := BaseOfCode + NTHeader.OptionalHeader.SizeOfCode;
  108.     TopOfStack := GetStackTop;
  109.   end;
  110. end;
  111.  
  112. function ValidStackAddr(StackAddr: DWORD): boolean;
  113. begin
  114.   Result := (BaseOfStack < StackAddr) and (StackAddr < TopOfStack);
  115. end;
  116.  
  117. function ValidCodeAddr(CodeAddr: DWORD): boolean;
  118. begin
  119.   Result := (BaseOfCode < CodeAddr) and  (CodeAddr < TopOfCode);
  120. end;
  121.  
  122. function ValidCallSite(CodeAddr: DWORD): boolean;
  123. // Validate that the code address is a valid code site
  124. //
  125. // Information from Intel Manual 24319102(2).pdf, Download the 6.5 MBs from:
  126. //  http://developer.intel.com/design/pentiumii/manuals/243191.htm
  127. //  Instruction format, Chapter 2 and The CALL instruction: page 3-53, 3-54
  128. var
  129.   CodeDWORD4: DWORD;
  130.   CodeDWORD8: DWORD;
  131. begin
  132.   // First check that the address is within range of our code segment! 
  133.   Result := (BaseOfCode < CodeAddr) and  (CodeAddr < TopOfCode);
  134.  
  135.   // Now check to see if the instruction preceeding the return address  
  136.   // could be a valid CALL instruction 
  137.   if Result then
  138.   begin
  139.     // Check the instruction prior to the potential call site. 
  140.     // We consider it a valid call site if we find a CALL instruction there 
  141.     // Check the most common CALL variants first 
  142.     CodeDWORD8 := PDWORD(CodeAddr-8)^;
  143.     CodeDWORD4 := PDWORD(CodeAddr-4)^;
  144.  
  145.     Result :=
  146.           ((CodeDWORD8 and $FF000000) = $E8000000) // 5-byte, CALL [-$1234567] 
  147.        or ((CodeDWORD4 and $38FF0000) = $10FF0000) // 2 byte, CALL EAX 
  148.        or ((CodeDWORD4 and $0038FF00) = $0010FF00) // 3 byte, CALL [EBP+0x8] 
  149.        or ((CodeDWORD4 and $000038FF) = $000010FF) // 4 byte, CALL ?? 
  150.        or ((CodeDWORD8 and $38FF0000) = $10FF0000) // 6-byte, CALL ?? 
  151.        or ((CodeDWORD8 and $0038FF00) = $0010FF00) // 7-byte, CALL [ESP-0x1234567] 
  152.     // It is possible to simulate a CALL by doing a PUSH followed by RET, 
  153.     // so we check for a RET just prior to the return address
  154.        or ((CodeDWORD4 and $FF000000) = $C3000000);// PUSH XX, RET 
  155.  
  156.     // Because we're not doing a complete disassembly, we will potentially report
  157.     // false positives. If there is odd code that uses the CALL 16:32 format, we 
  158.     // can also get false negatives. 
  159.  
  160.   end;
  161. end;
  162.  
  163. function NextStackFrame(var StackFrame: PStackFrame;
  164.                         var StackInfo : TStackInfo): boolean;
  165. begin
  166.   // Only report this stack frame into the StockInfo structure 
  167.   // if the StackFrame pointer, EBP on the stack and return 
  168.   // address on the stack are valid addresses 
  169.   while ValidStackAddr(DWORD(StackFrame)) do
  170.   begin
  171.     // CallerAdr within current process space, code segment etc. 
  172.     if ValidCodeAddr(StackFrame^.CallerAdr) then
  173.     begin
  174.       Inc(StackInfo.Level);
  175.       StackInfo.StackFrame := StackFrame;
  176.       StackInfo.ParamPtr   := PDWORDS(DWORD(StackFrame) + SizeOf(TStackFrame));
  177.       StackInfo.CallersEBP := StackFrame^.CallersEBP;
  178.       StackInfo.CallerAdr  := StackFrame^.CallerAdr;
  179.       StackInfo.DumpSize   := StackFrame^.CallersEBP - DWORD(StackFrame);
  180.       StackInfo.ParamSize  := (StackInfo.DumpSize - SizeOf(TStackFrame)) div 4;
  181.       // Step to the next stack frame by following the EBP pointer 
  182.       StackFrame           := PStackFrame(StackFrame^.CallersEBP);
  183.       Result := true;
  184.       Exit;
  185.     end;
  186.     // Step to the next stack frame by following the EBP pointer
  187.     StackFrame := PStackFrame(StackFrame^.CallersEBP);
  188.   end;
  189.   Result := false;
  190. end;
  191.  
  192. {$W+} // We must have stack-frames on for this routine
  193.  
  194. procedure TraceStackFrames(ReportStackFrame: TReportStackFrame; PrivateData: Pointer);
  195. var
  196.   StackFrame : PStackFrame;
  197.   StackInfo  : TStackInfo;
  198. begin
  199.   // Start at level 0 
  200.   StackInfo.Level := 0;
  201.  
  202.   // Make sure the global variables are correctly set 
  203.   InitGlobalVars;
  204.  
  205.   // Get the current stack fram from the EBP register 
  206.   StackFrame := GetEBP;
  207.  
  208.   // We define the bottom of the valid stack to be the current EBP Pointer 
  209.   // There is a TIB field called pvStackUserBase, but this includes more of the 
  210.   // stack than what would define valid stack frames. 
  211.   BaseOfStack := DWORD(StackFrame) - 1;
  212.  
  213.   // Loop over and report all valid stackframes
  214.   while NextStackFrame(StackFrame, StackInfo) and
  215.         ReportStackFrame(StackInfo, PrivateData) do
  216.     {Loop};
  217. end;
  218.  
  219. procedure TraceStackRaw(ReportStackFrame: TReportStackFrame; PrivateData: Pointer);
  220. var
  221.   StackInfo : TStackInfo;
  222.   StackPtr : PDWORD;
  223.   PrevCaller: DWORD;
  224. begin
  225.   // We define the bottom of the valid stack to be the current ESP pointer
  226.   BaseOfStack := DWORD(GetESP);
  227.  
  228.   // We will not be able to fill in all the fields in the StackInfo record,
  229.   // so just blank it all out first
  230.   FillChar(StackInfo, SizeOf(StackInfo), 0);
  231.  
  232.   // Make sure the global variables are correctly set
  233.   InitGlobalVars;
  234.  
  235.   // Clear the previous call address
  236.   PrevCaller := 0;
  237.  
  238.   // Get a pointer to the current bottom of the stack
  239.   StackPtr := PDWORD(BaseOfStack);
  240.  
  241.   // Loop through all of the valid stack space
  242.   while DWORD(StackPtr) < TopOfStack do
  243.   begin
  244.  
  245.     // If the current DWORD on the stack,
  246.     // refers to a valid call site...
  247.     if ValidCallSite(StackPtr^) and (StackPtr^ <> PrevCaller) then
  248.     begin
  249.       // then pick up the callers address 
  250.       StackInfo.CallerAdr := StackPtr^;
  251.  
  252.       // remeber to callers address so that we don't report it repeatedly 
  253.       PrevCaller := StackPtr^;
  254.  
  255.       // increase the stack level 
  256.       Inc(StackInfo.Level);
  257.  
  258.       // then report it back to our caller 
  259.       if not ReportStackFrame(StackInfo, PrivateData) then
  260.         Break;
  261.     end;
  262.  
  263.     // Look at the next DWORD on the stack 
  264.     Inc(StackPtr);
  265.   end;
  266. end;
  267.  
  268. function DefaultReportStackFrame(var StackInfo: TStackInfo; PrivateData: Pointer): boolean;
  269. begin
  270.   Result := (StackDumpCount < MaxStackLevels-1);
  271.   if Result                                 and  // We have an available slot 
  272.      (DWORD(PrivateData) < StackInfo.Level) then // We're not going to skip this level 
  273.   begin
  274.     // Save the contents of this stack frame
  275.     StackDump[StackDumpCount] := StackInfo;
  276.     Inc(StackDumpCount);
  277.   end;
  278. end;
  279.  
  280. procedure SaveStackTrace(Raw: boolean; IgnoreLevels: integer; FirstCaller: pointer);
  281. begin
  282.   FillChar(StackDump, SizeOf(StackDump), 0);
  283.   StackDumpCount := 0;
  284.   // Fill the first slot, if we are given an address directly
  285.   if Assigned(FirstCaller) then
  286.   begin
  287.     StackDump[0].CallerAdr := DWORD(FirstCaller);
  288.     StackDumpCount := 1;
  289.   end;
  290.   if Raw
  291.   then TraceStackRaw   (DefaultReportStackFrame, Pointer(IgnoreLevels))
  292.   else TraceStackFrames(DefaultReportStackFrame, Pointer(IgnoreLevels));
  293. end;
  294.  
  295. const
  296.   LinkerOffset = $1000;
  297.  
  298. function PhysicalToLogical(Physical: DWORD): DWORD;
  299. begin
  300.   Result :=   Physical
  301.             - DWORD(HInstance)
  302.             - LinkerOffset;
  303. end;
  304.  
  305. end.
  306.